/*
 * Copyright (c) 2011, Andrew Cole
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This class is heavily based on the Netbeans Xmi WriterBase class.
 * The writeInstance and writerReference methods have been modified to write
 * Xmi in a format compatible with that of Netbeans.
 */

package net.andycole.xmiwriter;

import org.netbeans.api.xmi.XMIReferenceProvider;
import org.netbeans.lib.jmi.xmi.OutputConfig;
import org.netbeans.lib.jmi.xmi.XmiConstants;
import org.netbeans.lib.jmi.xmi.WriterBase;

import javax.jmi.reflect.*;
import javax.jmi.model.*;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.netbeans.lib.jmi.util.Logger;

/**
 * Overrides writing of XMI version 1.x files to comply with the format
 * Netbeans expects for its .etd files.
 * 
 * @author Andrew Cole
 */
public class NetbeansXmiWriter extends WriterBase {

   public NetbeansXmiWriter(OutputConfig config)
   {
      super(config);
   }
   
   protected void writeIfNonAggregatedReference (RefObject obj, Reference ref) {
        // The netbeans uml datamodel includes references from child
        // items to its parent item. So we include aggregated referenced ends
        // for writing.
        //AggregationKind kind = ref.getReferencedEnd ().getAggregation ();
        //if (AggregationKindEnum.COMPOSITE.equals (kind))
        //   return; // do not serialize reference to immediate composite object
        AggregationKind kind = ref.getExposedEnd ().getAggregation ();
        boolean isComposite = AggregationKindEnum.COMPOSITE.equals (kind);
        if (isComposite)
           return;
        Object temp = obj.refGetValue (ref);
        if (temp == null)
            return;
        Collection values;
        if (isMultivalued (ref))
            values = (Collection) temp;
        else {
            values = new LinkedList ();
            values.add (temp);
        }
        Iterator iter;

        if (collectionWriting) {
            // exclude all referenced instances that are not in the transitive closure generated by input collection
            Collection cValues = new LinkedList ();
            iter = values.iterator ();
            while (iter.hasNext ()) {
                RefObject referencedObject = (RefObject) iter.next ();
                if (isInClosure (referencedObject)) {
                    cValues.add (referencedObject);
                } // if
            }  // while
            values = cValues;
        } // if

        if (values.isEmpty ())
            return;

        String refName = elementName (ref);
        // convert values into space-delimetered string
        writeInlineInstanceRefs (refName, values);
    }

      protected void writeIfAggregatedReference (RefObject obj, Reference ref) {
        AggregationKind kind = ref.getReferencedEnd ().getAggregation ();
        if (AggregationKindEnum.COMPOSITE.equals (kind))
            return; // do not serialize reference to immediate composite object
        kind = ref.getExposedEnd ().getAggregation ();
        boolean isComposite = AggregationKindEnum.COMPOSITE.equals (kind);
        if (!isComposite)
           return;
        Object temp = obj.refGetValue (ref);
        if (temp == null)
            return;
        Collection values;
        if (isMultivalued (ref))
            values = (Collection) temp;
        else {
            values = new LinkedList ();
            values.add (temp);
        }
        Iterator iter;

        if (collectionWriting) {
            // exclude all referenced instances that are not in the transitive closure generated by input collection
            Collection cValues = new LinkedList ();
            iter = values.iterator ();
            while (iter.hasNext ()) {
                RefObject referencedObject = (RefObject) iter.next ();
                if (isInClosure (referencedObject)) {
                    cValues.add (referencedObject);
                } // if
            }  // while
            values = cValues;
        } // if

        if (values.isEmpty ())
            return;

        String name = qualifiedName (ref);
        startElement (name);
        iter = values.iterator ();
        while (iter.hasNext ()) {
            RefObject endValue = (RefObject) iter.next ();
            //String tmp = elementName(ref);
            writeInstance (endValue, false);
        } // while
        endElement (name);
    }


   private void writeInlineInstanceRefs(String refName, Collection values)
   {
      String refList = "";

      Iterator iter = values.iterator ();
      RefObject obj = null;
      StringBuilder buffer = new StringBuilder();
      if (iter.hasNext())
      {
         obj = (RefObject) iter.next ();
         buffer.append(getAttributeValFromRef(obj));
      }

      while (iter.hasNext ()) {
         obj = (RefObject) iter.next ();
         buffer.append(" ");
         buffer.append(getAttributeValFromRef(obj));
      } // while
      refList = buffer.toString();
      addAttribute (refName, refList);
   }

   private String getAttributeValFromRef(RefObject obj)
   {
      XMIReferenceProvider.XMIReference xmiRef = provider.getReference (obj);
      String xmiId = xmiRef.getXmiId ();
      String systemId = xmiRef.getSystemId ();
      if ((systemId != null) && (thisSystemId != null) && (thisSystemId.equals (systemId)))
          systemId = null;
      if (systemId == null)
         return xmiId;
      return systemId + HREF_DELIMITER + xmiId;
   }

   @Override
   protected void writeNamespaces ()
   {
//        HashMap temp = new HashMap ();
//        Iterator iter = namespaces.entrySet ().iterator ();
//        while (iter.hasNext ()) {
//            String name = (String) ((Map.Entry) iter.next ()).getValue ();
//            if (temp.get (name) == null) {
//                temp.put (name, name);
//                addAttribute ("xmlns:" + name, XmiConstants.TAGID_XMI_NAMESPACE + "." + name);
//            } // if
//        } // while
      //addAttribute ("xmlns:" + name, XmiConstants.TAGID_XMI_NAMESPACE + "." + name);
      addAttribute ("xmlns:UML", "omg.org/UML/1.4");
   }

    @Override
    protected void writeInstance (RefObject obj, boolean isTop) {
        RefClass proxy = obj.refClass ();
        ModelElement element = (ModelElement) obj.refMetaObject ();
        String name = qualifiedName (element);
        XMIReferenceProvider.XMIReference xmiRef = provider.getReference (obj);
        String xmiId = xmiRef.getXmiId ();
        String systemId = xmiRef.getSystemId ();
        if ((systemId != null) && (thisSystemId != null) && (thisSystemId.equals (systemId)))
            systemId = null;

        markWritten (obj);
        if (systemId != null) { // object from an external document
            if (!isTop) { // serialize href
                startElement (name);
                addAttribute (XmiConstants.XMI_HREF, systemId + HREF_DELIMITER + xmiId);
                endElement (name);
            }
            collectLightOutermosts (obj, proxy);
            return;
        }

        startElement (name);
        addAttribute (XmiConstants.XMI_ID, xmiId);

        // Write non-aggregated references as attributes
        Iterator refs = references ((MofClass) proxy.refMetaObject ()).iterator ();
        while (refs.hasNext ()) {
            Reference ref = (Reference) refs.next ();
            writeIfNonAggregatedReference (obj, ref);
        }

        Iterator attrs = instanceAttributes ((MofClass) proxy.refMetaObject ()).iterator ();
        List attrsInContent = new LinkedList ();
        while (attrs.hasNext ()) {
            Attribute attr = (Attribute) attrs.next ();
            if (!VisibilityKindEnum.PUBLIC_VIS.equals(attr.getVisibility()))
                continue;
            boolean isMultivalued = isMultivalued (attr);
            Object value;
            try {
            value = obj.refGetValue (attr);
            } catch (Exception e) {
                Logger.getDefault().annotate(e, ((ModelElement) obj.refMetaObject ()).getName () + " " + attr.getName ());
                Logger.getDefault().notify(e);
                value = Boolean.FALSE; // [PENDING]
            }
            Object valueToWrite = value;
            if (value == null)
                continue; // optional attribute of no value
            if (isMultivalued) {
                Collection col = (Collection) value;
                if (col.size () > 0) {
                    attrsInContent.add (attr);
                }
                continue;
            } // if
            Classifier type = getType (attr);
            if (!(type instanceof PrimitiveType) && !(type instanceof EnumerationType)) {
                // values have to be serialized in content
                attrsInContent.add (attr);
            } else
                writeValueInAttr (attr, valueToWrite);
        } // while
        Iterator iter = attrsInContent.iterator ();
        while (iter.hasNext ()) {
            Attribute attr = (Attribute) iter.next ();
            writeValueInContent (attr, obj.refGetValue (attr));
        } // while

        refs = references ((MofClass) proxy.refMetaObject ()).iterator ();
        while (refs.hasNext ()) {
            Reference ref = (Reference) refs.next ();
            writeIfAggregatedReference (obj, ref);
        }

        endElement (name);
    }
}
